/*
 * The MIT License
 *
 * Copyright (c) 2009 André D. Piske
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
package org.gohtml.tree;

import java.io.Reader;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import org.gohtml.Element;
import org.gohtml.NodeBeginElement;
import org.gohtml.NodeEndElement;
import org.gohtml.Parser;
import org.gohtml.SingleNodeElement;
import org.gohtml.TextElement;
import org.gohtml.XmlNodeElement;

/**
 * @see buildTree()
 * @author André D. Piske
 * @since 0.1 alpha
 * @version 0.1 alpha
 */
public final class Builder {

    private Parser parser;
    private Node root;
    private Stack<Node> nodeStack;
    private Map<String, TagType> tagTypes;

    private Builder(Parser parser) {
        this.parser = parser;
        nodeStack = new Stack<Node>();

        tagTypes = new TreeMap<String, TagType>(new Comparator<String>() {

            public int compare(String o1, String o2) {
                return o1.compareToIgnoreCase(o2);
            }
        });


        Set<TagType> ttList = TagType.getHtml401Elements();
        for (TagType tt : ttList) {
            tagTypes.put(tt.getName(), tt);
        }
    }

    private TagType getTagType(String tagName) {
        return tagTypes.get(tagName);
    }

    private Node getStackTop() {
        if (nodeStack.isEmpty()) {
            return null;
        }
        return nodeStack.peek();
    }

    private Element getNextElement() {
        Element elem = null;
        while (elem == null) {
            elem = parser.next();
            if (elem == null && !parser.hasNext()) {
                return null;
            }
        }
        return elem;
    }

    private void onNodeBeginElement(NodeBeginElement elem) {
        Node node = new Node(elem.getName(), elem.getAttributeSet());
        TagType tagType = getTagType(node.getName());
        if (tagType == null) {
            tagType = new TagType(null, TagType.ATTR_BLOCK);
        }

        Node topNode = getStackTop();
        topNode.addChild(node);

        if (!tagType.isEmpty()) {
            nodeStack.push(node);
        }
    }

    private void closeNode(String nodeName) {
        int stackTop = nodeStack.size() - 1;

        /*TagType tt = getTagType(nodeName);
        if (tt == null) {
        tt = new TagType(null, TagType.ATTR_BLOCK);
        }*/

        for (int j = stackTop; j > 0; --j) {
            Node node = nodeStack.get(j);
            TagType nodeType = getTagType(node.getName());
            if (nodeType == null) {
                nodeType = new TagType(null, TagType.ATTR_BLOCK);
            }

            if (node.getName().compareToIgnoreCase(nodeName) == 0) {
                nodeStack.setSize(j);
                break;
            }

            if (nodeType.isBlock()) {
                break;
            }
        }
    }

    private void onNodeEndElement(NodeEndElement elem) {
        // TagType tagType = getTagType(elem.getName());
        closeNode(elem.getName());
    }

    private void onSingleNodeElement(SingleNodeElement elem) {
        // final boolean isXHTML = (elem instanceof XmlNodeElement);
        Node top = getStackTop();
        top.addChild(new Node(elem.getName(), elem.getAttributeSet()));
    }

    private void onTextElement(TextElement elem) {
        getStackTop().addChild(new NodeText(elem.toString()));
    }

    // @Recursive
    private void parseElements() {
        Element elem;
        while ((elem = getNextElement()) != null) {
            if (elem instanceof NodeBeginElement) {
                onNodeBeginElement((NodeBeginElement) elem);
            } else if (elem instanceof NodeEndElement) {
                onNodeEndElement((NodeEndElement) elem);
            } else if ((elem instanceof SingleNodeElement) || (elem instanceof XmlNodeElement)) {
                onSingleNodeElement((SingleNodeElement) elem);
            } else if (elem instanceof TextElement) {
                onTextElement((TextElement) elem);
            } else {
                // nothing here
            }
        }
    }

    private void startParsing() {
        nodeStack.push(root);
        parseElements();
    }

    private Node build() {
        root = new Node("<root>", null);
        startParsing();
        return root;
    }

    /**
     * Same as {@link buildTree()}, but directly from a Reader.<br>
     * The document will be parsed first using a {@link org.gohtml.Parser}
     *
     * @see buildTree()
     * @see org.gohtml.Parser
     */
    public static Node buildTreeFromReader(Reader reader) {
        return buildTree(new Parser(reader));
    }

    /**
     * Builds a tree (like in DOM) of nodes.
     *
     * @see org.gohtml.tree.Node
     */
    public static Node buildTree(Parser parser) {
        Builder builder = new Builder(parser);
        return builder.build();
    }
}
